use std::collections::HashSet;

use rstest::rstest;

use super::super::*;
use crate::tests::fixtures::{rand_usize, random_connectors, RANDOM_USIZE};

#[rstest]
fn test_direction_raw_initialization(
    #[values(Direction::North, Direction::East, Direction::South, Direction::West)]
    direction: Direction,
) {
    match direction {
        // The following match will fail if the variant is missing or is not a unit variant.
        Direction::North | Direction::East | Direction::South | Direction::West => (),
    }
}

#[rstest]
fn test_grid_slot_raw_initialization(
    #[values(None, Some(0), Some(1445))] tile_id: Option<usize>,
    #[values(None, Some(Direction::West), Some(Direction::East))] direction: Option<Direction>,
    #[values(None, Some((1, 3)))] predecessor: Option<(usize, usize)>,
    #[values(
        vec![],
        vec![Direction::West, Direction::North, Direction::East],
    )]
    exposed_edges: Vec<Direction>,
    #[values(
        HashSet::new(),
        {
            let mut excluded_connectors = HashSet::new();
            excluded_connectors.insert(random_connectors());
            excluded_connectors.insert(random_connectors());
            excluded_connectors
        }
    )]
    excluded_connectors: HashSet<Connectors>,
) {
    let grid_slot = GridSlot {
        tile_id,
        direction,
        predecessor,
        exposed_edges: exposed_edges.clone(),
        excluded_connectors: excluded_connectors.clone(),
    };

    assert_eq!(grid_slot.tile_id, tile_id);
    assert_eq!(grid_slot.direction, direction);
    assert_eq!(grid_slot.predecessor, predecessor);
    assert_eq!(grid_slot.exposed_edges, exposed_edges);
    assert_eq!(grid_slot.excluded_connectors, excluded_connectors);
}

#[rstest]
fn test_grid_slot_new(
    #[values(
        None,
        Some((rand_usize(1, 10), rand_usize(1, 10))),
    )]
    predecessor: Option<(usize, usize)>,
) {
    let grid_slot: GridSlot = GridSlot::new(predecessor);

    assert_eq!(grid_slot.tile_id, None);
    assert_eq!(grid_slot.direction, None);
    assert_eq!(grid_slot.predecessor, predecessor);
    assert_eq!(grid_slot.exposed_edges, vec![]);
    assert_eq!(grid_slot.excluded_connectors, HashSet::new());
}

#[test]
fn test_grid_slot_with_successor() {
    let _grid_slot = GridSlot {
        tile_id: Some(3),
        direction: Some(Direction::North),
        predecessor: None,
        exposed_edges: vec![],
        excluded_connectors: HashSet::new(),
    };
    let _second_slot = GridSlot {
        tile_id: Some(45),
        direction: Some(Direction::East),
        predecessor: Some((3, 4)),
        exposed_edges: vec![],
        excluded_connectors: HashSet::new(),
    };
}

#[rstest]
#[case::no_exposed_edges(
    vec![],
    Direction::North,
    vec![Direction::North],
)]
#[case::two_exposed_edges(
    vec![Direction::West, Direction::South],
    Direction::East,
    vec![Direction::West, Direction::South, Direction::East],
)]
#[case::three_exposed_edges(
    vec![Direction::North, Direction::West, Direction::East],
    Direction::South,
    vec![Direction::North, Direction::West, Direction::East, Direction::South],
)]
fn test_grid_slot_add_exposed_edge(
    #[case] exposed_edges: Vec<Direction>,
    #[case] side: Direction,
    #[case] expected_exposed_edges: Vec<Direction>,
) {
    let mut grid_slot = GridSlot {
        tile_id: Some(rand_usize(3, 9)),
        direction: Some(Direction::West),
        predecessor: Some((7, 2)),
        exposed_edges,
        excluded_connectors: HashSet::new(),
    };
    let _: () = grid_slot.add_exposed_edge(side);

    assert_eq!(grid_slot.exposed_edges, expected_exposed_edges);
}

#[rstest]
#[case::no_exposed_edges(vec![], Direction::West, vec![])]
#[case::one_exposed_edge(
    vec![Direction::South],
    Direction::South,
    vec![],
)]
#[case::four_exposed_edges(
    vec![Direction::West, Direction::North, Direction::East],
    Direction::South,
    vec![Direction::West, Direction::North, Direction::East],
)]
#[case::four_exposed_edges(
    vec![Direction::North, Direction::West, Direction::East, Direction::South],
    Direction::North,
    vec![Direction::West, Direction::East, Direction::South],
)]
fn test_grid_slot_remove_exposed_edge(
    #[case] exposed_edges: Vec<Direction>,
    #[case] side: Direction,
    #[case] expected_exposed_edges: Vec<Direction>,
) {
    let mut grid_slot = GridSlot {
        tile_id: Some(rand_usize(5, 10)),
        direction: Some(Direction::North),
        predecessor: Some((7, 6)),
        exposed_edges,
        excluded_connectors: HashSet::new(),
    };
    let _: () = grid_slot.remove_exposed_edge(side);

    assert_eq!(grid_slot.exposed_edges, expected_exposed_edges);
}

#[rstest]
#[case::no_exposed_edges(vec![])]
#[case::one_exposed_edge(vec![Direction::North])]
#[case::two_exposed_edges(vec![Direction::West, Direction::East])]
#[case::three_exposed_edges(vec![Direction::North, Direction::East, Direction::West])]
#[case::four_exposed_edges(
    vec![Direction::North, Direction::East, Direction::South, Direction::West],
)]
fn test_grid_slot_next_exposed_edge(#[case] exposed_edges: Vec<Direction>) {
    let grid_slot = GridSlot {
        tile_id: Some(rand_usize(5, 10)),
        direction: Some(Direction::North),
        predecessor: Some((7, 6)),
        exposed_edges: exposed_edges.clone(),
        excluded_connectors: HashSet::new(),
    };
    let side: Option<Direction> = grid_slot.next_exposed_edge();

    if exposed_edges.is_empty() {
        assert_eq!(side, None);
    } else {
        assert!(exposed_edges.contains(&side.expect("side should be Some(Direction)")));
    }
    assert_eq!(grid_slot.exposed_edges, exposed_edges);
}

#[rstest]
fn test_grid_slot_clear_exposed_edges(
    #[values(
        vec![],
        vec![Direction::North],
        vec![Direction::North, Direction::West],
        vec![Direction::South, Direction::West],
        vec![Direction::South, Direction::East, Direction::West],
    )]
    exposed_edges: Vec<Direction>,
) {
    let mut grid_slot = GridSlot {
        tile_id: Some(rand_usize(9, 11)),
        direction: Some(Direction::North),
        predecessor: None,
        exposed_edges,
        excluded_connectors: HashSet::new(),
    };
    grid_slot.clear_exposed_edges();

    assert!(grid_slot.exposed_edges.is_empty());
}

// noinspection DuplicatedCode
#[rstest]
#[case::zero_excluded_connectors(
    HashSet::new(),
    Connectors {
        north: *RANDOM_USIZE + 7,
        east: *RANDOM_USIZE + 2,
        south: *RANDOM_USIZE + 1,
        west: *RANDOM_USIZE + 9,
    },
    HashSet::from([
        Connectors {
            north: *RANDOM_USIZE + 7,
            east: *RANDOM_USIZE + 2,
            south: *RANDOM_USIZE + 1,
            west: *RANDOM_USIZE + 9,
        },
    ]),
)]
#[case::three_excluded_connectors(
    HashSet::from([
        Connectors {
            north: *RANDOM_USIZE + 3,
            east: *RANDOM_USIZE + 9,
            south: *RANDOM_USIZE + 4,
            west: *RANDOM_USIZE + 6,
        },
        Connectors {
            north: *RANDOM_USIZE + 1,
            east: *RANDOM_USIZE + 4,
            south: *RANDOM_USIZE + 4,
            west: *RANDOM_USIZE + 3,
        },
        Connectors {
            north: *RANDOM_USIZE + 1,
            east: *RANDOM_USIZE + 2,
            south: *RANDOM_USIZE + 4,
            west: *RANDOM_USIZE + 8,
        },
    ]),
    Connectors {
        north: *RANDOM_USIZE + 1,
        east: *RANDOM_USIZE + 1,
        south: *RANDOM_USIZE + 2,
        west: *RANDOM_USIZE + 11,
    },
    HashSet::from([
        Connectors {
            north: *RANDOM_USIZE + 3,
            east: *RANDOM_USIZE + 9,
            south: *RANDOM_USIZE + 4,
            west: *RANDOM_USIZE + 6,
        },
        Connectors {
            north: *RANDOM_USIZE + 1,
            east: *RANDOM_USIZE + 4,
            south: *RANDOM_USIZE + 4,
            west: *RANDOM_USIZE + 3,
        },
        Connectors {
            north: *RANDOM_USIZE + 1,
            east: *RANDOM_USIZE + 2,
            south: *RANDOM_USIZE + 4,
            west: *RANDOM_USIZE + 8,
        },
        Connectors {
            north: *RANDOM_USIZE + 1,
            east: *RANDOM_USIZE + 1,
            south: *RANDOM_USIZE + 2,
            west: *RANDOM_USIZE + 11,
        },
    ]),
)]
#[case::excluded_connectors_already_present(
    HashSet::from([
        Connectors {
            north: *RANDOM_USIZE + 4,
            east: *RANDOM_USIZE + 9,
            south: *RANDOM_USIZE + 4,
            west: *RANDOM_USIZE + 6,
        },
        Connectors {
            north: *RANDOM_USIZE + 1,
            east: *RANDOM_USIZE + 4,
            south: *RANDOM_USIZE + 4,
            west: *RANDOM_USIZE + 3,
        },
        Connectors {
            north: *RANDOM_USIZE + 1,
            east: *RANDOM_USIZE + 2,
            south: *RANDOM_USIZE + 4,
            west: *RANDOM_USIZE + 8,
        },
        Connectors {
            north: *RANDOM_USIZE + 1,
            east: *RANDOM_USIZE + 1,
            south: *RANDOM_USIZE + 2,
            west: *RANDOM_USIZE + 11,
        },
    ]),
    Connectors {
        north: *RANDOM_USIZE + 1,
        east: *RANDOM_USIZE + 1,
        south: *RANDOM_USIZE + 2,
        west: *RANDOM_USIZE + 11,
    },
    HashSet::from([
        Connectors {
            north: *RANDOM_USIZE + 4,
            east: *RANDOM_USIZE + 9,
            south: *RANDOM_USIZE + 4,
            west: *RANDOM_USIZE + 6,
        },
        Connectors {
            north: *RANDOM_USIZE + 1,
            east: *RANDOM_USIZE + 4,
            south: *RANDOM_USIZE + 4,
            west: *RANDOM_USIZE + 3,
        },
        Connectors {
            north: *RANDOM_USIZE + 1,
            east: *RANDOM_USIZE + 2,
            south: *RANDOM_USIZE + 4,
            west: *RANDOM_USIZE + 8,
        },
        Connectors {
            north: *RANDOM_USIZE + 1,
            east: *RANDOM_USIZE + 1,
            south: *RANDOM_USIZE + 2,
            west: *RANDOM_USIZE + 11,
        },
    ]),
)]
fn test_grid_slot_add_excluded_connectors(
    #[case] excluded_connectors: HashSet<Connectors>,
    #[case] connectors: Connectors,
    #[case] expected_excluded_connectors: HashSet<Connectors>,
) {
    let mut grid_slot = GridSlot {
        tile_id: Some(rand_usize(4, 7)),
        direction: Some(Direction::South),
        predecessor: Some((8, 9)),
        exposed_edges: vec![Direction::West],
        excluded_connectors,
    };
    let _: () = grid_slot.add_excluded_connectors(connectors);

    assert_eq!(grid_slot.excluded_connectors, expected_excluded_connectors);
}
